home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 090 / byte0387.arc / EDGINTON.ARC / LIST2.TXT < prev    next >
Text File  |  1986-07-16  |  12KB  |  210 lines

  1.  
  2.          This  project  started  as  a  challenge  to  make a friend's
  3.     calculator program load and remain resident in memory  on  an  IBM
  4.     PC.   Making  a program written in assembly langauge stay resident
  5.     has been presented in many articles and  books,  but  writing  the
  6.     tools  to  make  a  C  program  resident  was  a new adventure.  I
  7.     developed all the  examples  in  this  article  with  Lattice's  C
  8.     Compiler  version 3.0 and Microsoft's Macro Assembler 4.0.  I have
  9.     tried to make everything as portable as  possible,  but  I'm  sure
  10.     that  some  modification  will  have  to  be  made  for  different
  11.     compilers and languages.   In  the  listings,  I  have  noted  any
  12.     compiler-dependent  variables.   (Editor's  note:  William Claff's
  13.     article, "xxxxxx" on page ??  contains additional  information  on
  14.     the topic of DOS extension via memory-resident programs.)
  15.  
  16.     WHAT IS A RESIDENT PROGRAM?
  17.  
  18.          DOS  uses  a  set  of  pointers called Storage Blocks to keep
  19.     track of allocated and unallocated memory in the system.  For each
  20.     loaded program,  these  pointers  indicate  the  address  its  PSP
  21.     (Program  Segment Prefix) the program's length in segments.  There
  22.     is also a flag that indicates whether or not the memory pointed to
  23.     by the Storage Block is  allocated.   When  a  program  module  is
  24.     loaded  and  executes  an INT 27H (terminate but stay resident) or
  25.     DOS function 31H (keep process), COMMAND.COM makes sure that  this
  26.     program becomes a part of DOS.  This means that the Storage Block,
  27.     PSP,  and  the  program  module  remain  in  memory  and  are  not
  28.     reallocated.
  29.  
  30.          The  principles  behind making a program resident seems to be
  31.     straightforward, just find the length of  the  program,  shove  it
  32.     into  a register and call a documented function.  DOS Function 31H
  33.     requests the program size in paragraphs be placed in  DX  and  the
  34.     return code if any in AL.
  35.  
  36.          As  demonstrated  by  the program shown in listing 1, it is a
  37.     simple matter to make a program resident.  If you have  a  utility
  38.     like Norton's SI or SMAP you can verify that the program is indeed
  39.     resident  by  looking  at  the  location of the next program to be
  40.     loaded address.  You can also examine the amount  of  free  memory
  41.     displayed  by  the  CHKDSK  utility  before  and after running the
  42.     program.
  43.  
  44.          Usually, we want to write a program that is more helpful than
  45.     just  taking  up memory.  Specifically, we want to write a program
  46.     that responds to a system interrupt, and in doing so, supplies  us
  47.     with  some  sort of information.  It should also be "well behaved"
  48.     and operate within the constraints of DOS.
  49.  
  50.          The  design  of  this installation system had several primary
  51.     goals:
  52.  
  53.     * Modular design for universal application.
  54.     * Optimum memory usage.
  55.     * Correct processing of interrupts.    
  56.  
  57.          Modular  design  means  that I can, with minor revision, make
  58.     this program load any module that meets with the requirements  for
  59.     a  resident,  interrupt  processing  program.   To determine these
  60.     requirements, I made a careful analysis of what my compiler did to
  61.     a program, and what my linker did to the object  modules  supplied
  62.     to  it.   If you are using a compiler and/or linker other than the
  63.     ones I used, these requirements may be different.  Listing 2 is an
  64.     example of a  completed  sample  system.   Since  we  have  little
  65.     control  right  now over anything that happens above main(), we'll
  66.     start there and analyze what happens.  Refer to listing  3,  which
  67.     is a dissasembled version of the top-level code in listing 2.
  68.  
  69.          Cpush()  and  cpop()  are  two routines we'll create later to
  70.     help us get into and return from the interrupts.  Since main()  is
  71.     really just another function called by the compiler's entry module
  72.     which  is  what is really loaded by EXEC, the BP register is saved
  73.     and then set to  the  new  SP.   This  is  a  requirement  of  any
  74.     functions   called  from  another  routine  that  might  pass  any
  75.     information on the stack; it allows  the  functions  to  reference
  76.     that  information  on  the  stack  via the BP register while still
  77.     permitting new data to be pushed on the stack as required.
  78.  
  79.          Entry  into  a  resident  program  should  be designed so any
  80.     parameters are  passed  in  the  DOS  communications  area  or  in
  81.     registers,  and  not on the stack.  Also, once a program module is
  82.     installed in memory, we want to  ignore  the  call  to  install().
  83.     Although this uses six bytes of memory, passing the address of the
  84.     call  to cpush() to the interrupt vector is the most efficient way
  85.     to install the module.  All function names are made common in a  C
  86.     compiler so we can create the new vector IP by:
  87.  
  88.     nu_entry = (short)main + 6;
  89.  
  90.          Casting  main to a short keeps it consistent with the way the
  91.     rest of the register structures are typed.  nu_entry now points to
  92.     the desired entry point in the program.  Since we did not need  to
  93.     use  the  compiler-generated PUSH BP, and we are returning from an
  94.     interrupt we can ignore the POP BP and the RET that  the  compiler
  95.     put at the end of main.
  96.  
  97.          The install() function is straightforward.  In this example I
  98.     borrowed  an unused function call's vector to leave a signature or
  99.     message to the calling program that we are already installed.   To
  100.     increase  the  safety  of  this routine, you could verify that the
  101.     interrupt vector is filled with zeros first.  If it is not,  check
  102.     another   vector  until  one  is  found  with  no  vector  already
  103.     installed.  Alternately, you could indicate  that  the  module  is
  104.     already  installed by setting a flag in memory, but you would have
  105.     to choose a byte that you are certain would not be  used  by  some
  106.     other routine.
  107.  
  108.          Another  method  for routines that handle passed values (i.e.
  109.     video calls, put and get char and string calls) would be to detect
  110.     a certain value, and return an 'already installed' message to  the
  111.     installation  program.  Listing 4 shows a segment of code that you
  112.     could modify to perform this method of signature detection.
  113.  
  114.          The  next  task  is  to decide how to best utilize the memory
  115.     taken up by the program.  Since I used function call  31h  instead
  116.     of  int  21h  to terminate the program, loaded programs can exceed
  117.     the 64k limit imposed by the latter.  I can use .EXE programs with
  118.     stack and data segments defined -- not just .COM programs.  A .COM
  119.     program uses as much memory as the machine has  left  when  it  is
  120.     loaded; if the program is going to stay resident, it has to return
  121.     its unused memory to the system.
  122.  
  123.          I  release the memory that contains the program's copy of the
  124.     environment using routine d_env().  On entry, the ES and  DS  (and
  125.     SS  and  CS for a .COM program) segment registers point to the PSP
  126.     at offset 0.  Listing 5 shows the code for  d_env().   I  load  ES
  127.     with  the  address  of  the  segment  containing  the  copy of the
  128.     environment and call DOS function 49H (free allocated memory).
  129.  
  130.          If  the program is a .COM file, you can reduce its size using
  131.     routine shrink() (see listing 6).  This function sets  the  memory
  132.     used by a program to the size of the program module in paragraphs.
  133.     If  you  write .COM programs, be sure that you allocate stack area
  134.     before calling this function.  If you  use  shrink(),  you  should
  135.     call  it  before  calling d_env() so that the ES register contains
  136.     the correct information for  the  call  to  function  4AH  (modify
  137.     allocated  memory  blocks).  (You could modify the code to perform
  138.     both operations with one call to increase the speed and reduce the
  139.     size of the program.)
  140.  
  141.          The  last  area  I will cover concerning memory management is
  142.     one that is  heavily  influenced  with  my  familiarity  with  the
  143.     Lattice compiler.  This compiler uses a file to set up the segment
  144.     registers, handle stack and memory allocations, report errors such
  145.     as  stack  overflows,  handle command line arguments to change the
  146.     stack size, redirect I/O, and some  other  incidental  operations.
  147.     The  code  for  all  this is found in the c.asm file -- its object
  148.     module is in c.obj.  This code is loaded before main() and  cannot
  149.     be  efficiently  deallocated  by  any  means  other  than actually
  150.     editing out unused portions of c.asm and recompiling the file.   A
  151.     knowledgeable  programmer  should be able to remove large portions
  152.     of c.asm for many applications; I have reduced considerable  space
  153.     in mine.
  154.  
  155.     INTERRUPTS
  156.  
  157.          Now that I've shown how to load programs into memory and keep
  158.     them  resident,  let's examine the available methods of processing
  159.     the interrupts (keyboard, clock, etc.)   and  determine  the  best
  160.     possible way to maintain 'nice' programs.  My main concern is with
  161.     the  saving  of registers and flags because of the amount of calls
  162.     and subroutines normally found in a program written  in  C.   (You
  163.     can see an example of this in listing 1.)
  164.  
  165.          Since  we  passed  the  address  of  cpush() to the interrupt
  166.     vector, the first thing the program does when it is entered  is  a
  167.     call  to cpush().  This call pushes a return address on the stack,
  168.     one that would not be there if the code  was  being  generated  in
  169.     assembly  language.   This  problem  is  repeated  throughout  the
  170.     program so it must be handled very early on.
  171.  
  172.          The  three  modules  in listing 7 show one of the fastest and
  173.     most efficient solutions I found.  Upon entry into the  program  I
  174.     call  cpush().  This routine stores the short call return address,
  175.     the interrupting program's return CS and IP, and  the  FLAGS  that
  176.     are pushed on the stack.  It then stores the registers and segment
  177.     registers  in  its own allocated memory.  The short return address
  178.     is then pushed back onto the stack and the function returns to the
  179.     body of the program.
  180.  
  181.          After  the  interrupt  routine  does its work (in the example
  182.     given in listing 2, it prints "Hello world"), it calls  cpop()  to
  183.     return  to the interrupted program.  The cpop() routine emulates a
  184.     pop of all the registers that should have  been  pushed  onto  the
  185.     stack  upon  entry  into  the  interrupt handler, and then does an
  186.     IRET.  (For debugging purposes, I have also included the code  for
  187.     cpopt(),  which is similar to cpop() except that cpopt() exits via
  188.     a RET instruction.)
  189.  
  190.     SUMMARY
  191.  
  192.          This  article demonstrates a very simple interrupt processing
  193.     program that remains resident in memory.  I plan to do  more  work
  194.     in   writing   programs   that  process  the  keyboard  and  video
  195.     interrupts.  Any programs written that use these techniques should
  196.     be written with proper attention to good  program  structure,  and
  197.     correct  manipulation  of  pointers  and  addresses.  This project
  198.     turned out to be a lot more ambitious than I  originally  thought.
  199.     The  entry  and  exit routines posed the most problem, testing and
  200.     debugging sometimes left the machine in a  very  corrupted  state.
  201.     Be  certain  that your C programs can pass lint before using them;
  202.     remember, you are creating a extension of DOS.  I did notice  that
  203.     including  structures in a program compiled with Lattice increased
  204.     the address of the entry point by three.  It makes  a  call  after
  205.     main() to set up the memory for the structs and/or unions.  I will
  206.     be  interested in feedback about improving any of these algorithms
  207.     and techniques.
  208.  
  209.  
  210.